通过DDCTF两道逆向题练习脱壳

明天出发去打西湖论剑线下了,然后今天守望又出了PVE,心有点不定,啥都不想做。。。不想浪费时间,刚好最近做了DDCTF的前两道逆向都是脱壳的,手动脱壳还没试过,就记下来。(其实是因为第一题UPX工具脱壳脱出来不好所以手动orz)

前置工作:

之前还没用过IDA在windows上调试,都是用OD或者xdbg。这次本来也想在OD脱的,结果太菜了一进去就不知道是什么情况了。。。想着还是IDA舒服,趁机学一下。结合IDA在linux的调试要开调试进程,在IDA的文件夹下搜到了这个:

这题是32位的,所以运行最上面的那个。然后在IDA选择Debugger->Select debugger->Local Windows debugger。


reverse1_final:

先IDA分析,段名称叫UPX1、UPX2,因此很大可能是UPX壳。PEID查了一下也确实是UPX壳。前面一堆压缩的信息,没什么意义。直接看后面。

一开始就是个pusha,所以可以用esp定律脱壳,可以在pusha后,对当前esp所在位置下硬件访问断点。F9执行。

F9完跳到0x917C47,后面有个小循环,所以在0x917C51下个断点再F9,就出来了。一直单步,看到运行到12AA就停了,看控制台等待输入,因此这个应该是主要运行程序。跟进去看,发现能F5了,而且有提示输出和读入数据,因此这个大概率是main函数。但是只有printf和scanf,感觉分析不完全,再到汇编看看。

看上去不是正常的结尾。再往后看还有一些零零散散的程序。对程序碎片进行C操作,组合成程序块方便后面操作。因此把当前函数和后面的部分函数U掉,再重新P一下进行函数分析,F5看下,就能得到看上去较为正常的程序。

看上去主要的运算都在DD1000那个函数,进去看看。点进去看什么都没有,到汇编层面看看。看到也是零零散散的程序。C一下直接对DD1000函数进行F5,直接能分析出来了。

后面都是非常简单的逻辑了。将输入和常量表操作一下,然后和DDCTF{reverseME}进行比较,正确就过check了。只要逆运算一下就能得到正确输入,即flag。

脚本:

flag="DDCTF{reverseME}"
constant=[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xBA,0xA5,0x0D,0x34,0x45,0x5A,0xF2,0xCB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFE,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x7E,0x7D,0x7C,0x7B,0x7A,0x79,0x78,0x77,
0x76,0x75,0x74,0x73,0x72,0x71,0x70,0x6F,0x6E,0x6D,0x6C,0x6B,0x6A,0x69,0x68,0x67,
0x66,0x65,0x64,0x63,0x62,0x61,0x60,0x5F,0x5E,0x5D,0x5C,0x5B,0x5A,0x59,0x58,0x57,
0x56,0x55,0x54,0x53,0x52,0x51,0x50,0x4F,0x4E,0x4D,0x4C,0x4B,0x4A,0x49,0x48,0x47,
0x46,0x45,0x44,0x43,0x42,0x41,0x40,0x3F,0x3E,0x3D,0x3C,0x3B,0x3A,0x39,0x38,0x37,
0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x2F,0x2E,0x2D,0x2C,0x2B,0x2A,0x29,0x28,0x27,
0x26,0x25,0x24,0x23,0x22,0x21,0x20,0x00,0x01,0x00,0x00,0x00,0x00,0x1B,0xD5,0x00]
print len(constant)

origin=""
for i in flag:
    origin+=chr(constant[ord(i)])
print origin

reverse2_final:

peid查一下,发现又加了壳。这次是ASPack壳。

跟上面一样,不过这次我直接找了popa,其实原理是一样的。

脱壳完后单步调试。看到明显的提示输入字符串,之后有一些零零碎碎的数据,老规矩C一下,U掉再P,分析函数,再F5即可。

关键函数在1271240处。里面逻辑也很简单,这次主要为了脱壳,就不再分析了。

脚本:

flag="reverse+"
origin=""
constant=[0x37,0x34,0x35,0x32,0x33,0x30,0x31,0x3E,0x3F,0x3C,0x3D,0x3A,0x3B,0x38,0x39,0x26,
0x27,0x24,0x25,0x22,0x23,0x20,0x21,0x2E,0x2F,0x2C,0x17,0x14,0x15,0x12,0x13,0x10,
0x11,0x1E,0x1F,0x1C,0x1D,0x1A,0x1B,0x18,0x19,0x06,0x07,0x04,0x05,0x02,0x03,0x00,
0x01,0x0E,0x0F,0x0C,0x46,0x47,0x44,0x45,0x42,0x43,0x40,0x41,0x4E,0x4F,0x5D,0x59,
0x01,0x00,0x00,0x00,0xA8,0x1A,0x73,0x01,0xA0,0x37,0x73,0x01,0x00,0x00,0x00,0x00]

def change(one):
    if one>=0 and one<=9:
        return one+48
    elif one>=10 and one<=15:
        return one+55

backone=[]
for i in flag:
    temp=ord(i)^0x76
    temp=constant.index(temp)
    backone.append(temp)
    print hex(temp),
print ""
backtwo=""
for i in range(0,len(backone),4):
    a=backone[i]<<2
    a+=((backone[i+1]&0xf0)>>4)&0x3
    b=(backone[i+1]&0xf)<<4
    b+=(backone[i+2]&0x3c)>>2
    c=(backone[i+2]&0x3)<<6
    c+=backone[i+3]
    print hex(a),hex(b),hex(c)
    backtwo+=chr(a)
    backtwo+=chr(b)
    backtwo+=chr(c)

PS:

这个是复现的记录,基本跟做的时候操作一样。不过一开始做的时候有一次栈分析错误,复现的时候没有出现。下面补充下解决方法。

先Options->General->Disassembly->Stack pointer勾选上。之后在汇编指令隔壁能看到多了些数字,这个是当前栈指针相对ebp的偏移。

然后观察函数尾部,对于把平衡栈任务交给被调用者的函数调用,函数执行到retn时,esp应该为0。如果发现esp不为0说明有问题。这时我们可以手动调整。Edit->Functions->Change stack pointer,或者快捷键ALT+K,把指定行的esp调整,直到最后esp偏移为0即可。